package com.jia.train.seat.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.jia.train.global.context.AuthContextHolder;
import com.jia.train.global.enums.BusinessEnum;
import com.jia.train.global.exception.BusinessException;
import com.jia.train.global.utils.SnowIdUtil;
import com.jia.train.seat.entity.ConfirmOrder;
import com.jia.train.seat.entity.DailyTrainCarriage;
import com.jia.train.seat.entity.DailyTrainSeat;
import com.jia.train.seat.entity.DailyTrainTicket;
import com.jia.train.seat.enums.ConfirmOrderStatusEnum;
import com.jia.train.seat.enums.SeatColEnum;
import com.jia.train.seat.enums.SeatTypeEnum;
import com.jia.train.seat.mapper.ConfirmOrderMapper;
import com.jia.train.seat.request.ConfirmOrderDoRequest;
import com.jia.train.seat.request.ConfirmOrderTicketRequest;
import com.jia.train.seat.service.*;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class ConfirmOrderServiceImpl implements ConfirmOrderService {

    private final TransactionTemplate transactionTemplate;

    private final ConfirmOrderMapper confirmOrderMapper;

    private final DailyTrainTicketService dailyTrainTicketService;

    private final DailyTrainCarriageService dailyTrainCarriageService;

    private final DailyTrainSeatService dailyTrainSeatService;

    private final AfterConfirmOrderService afterConfirmOrderService;

    private final StringRedisTemplate redisTemplate;

    private final RedissonClient redissonClient;

    public ConfirmOrderServiceImpl(TransactionTemplate transactionTemplate,
                                   ConfirmOrderMapper confirmOrderMapper,
                                   DailyTrainTicketService dailyTrainTicketService,
                                   DailyTrainCarriageService dailyTrainCarriageService,
                                   DailyTrainSeatService dailyTrainSeatService,
                                   AfterConfirmOrderService afterConfirmOrderService,
                                   StringRedisTemplate redisTemplate, RedissonClient redissonClient) {
        this.transactionTemplate = transactionTemplate;
        this.confirmOrderMapper = confirmOrderMapper;
        this.dailyTrainTicketService = dailyTrainTicketService;
        this.dailyTrainCarriageService = dailyTrainCarriageService;
        this.dailyTrainSeatService = dailyTrainSeatService;
        this.afterConfirmOrderService = afterConfirmOrderService;
        this.redisTemplate = redisTemplate;
        this.redissonClient = redissonClient;
    }

    public static final Logger logger = LoggerFactory.getLogger(ConfirmOrderServiceImpl.class);

    @Override
    public synchronized void doSyncConfirm(ConfirmOrderDoRequest request) {
        // 单节点会降低吞吐量
        // 多节点依旧会有超卖问题
        doConfirm(request);
    }

    @Override
    public void doCacheConfirm(ConfirmOrderDoRequest request) {
        String tripCode = request.getTripCode();
        String date = DateUtil.formatDate(request.getDate());
        String key = date + "-" + tripCode;
        String threadId = String.valueOf(Thread.currentThread().getId());
        try {
            Boolean setIfAbsent = redisTemplate.opsForValue()
                    .setIfAbsent(key, threadId, 3, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(setIfAbsent)) {
                logger.info("恭喜线程：{} 抢到锁了", threadId);
                doConfirm(request);
            } else {
                logger.info("可惜没有抢到锁");
            }
        } finally {
            String value = redisTemplate.opsForValue().get(key);
            if (StringUtils.hasText(value) && value.equals(threadId)) {
                redisTemplate.delete(value);
                logger.info("线程：{} 购票流程结束，释放锁", threadId);
            }
        }

    }

    @Override
    public void doDogConfirm(ConfirmOrderDoRequest request) {
        String tripCode = request.getTripCode();
        String date = DateUtil.formatDate(request.getDate());
        String key = date + "-" + tripCode;
        RLock lock = null;
        try {
            lock = redissonClient.getLock(key);
            boolean tryLock = lock.tryLock(0, TimeUnit.SECONDS);
            if (tryLock) {
                logger.info("恭喜抢到锁了");
                doConfirm(request);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
                logger.info("购票流程结束，释放锁");
            }

        }
    }

    @Override
    public void doRedConfirm(ConfirmOrderDoRequest request) {
        String tripCode = request.getTripCode();
        String date = DateUtil.formatDate(request.getDate());
        String key = date + "-" + tripCode;
        RLock lock = null;

        try {
            lock = redissonClient.getLock(key);
            RedissonRedLock redLock = new RedissonRedLock(lock, lock, lock);
            boolean tryLock = redLock.tryLock(0, TimeUnit.SECONDS);
            if (tryLock) {
                logger.info("恭喜抢到锁了");
                doConfirm(request);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
                logger.info("购票流程结束，释放锁");
            }

        }
    }

    @Override
    @SentinelResource(value = "doSentinelConfirm", blockHandler = "doSentinelConfirmBlock")
    public void doSentinelConfirm(ConfirmOrderDoRequest request) {
        doConfirm(request);
    }

    private void doSentinelConfirmBlock(ConfirmOrderDoRequest request, BlockException e) {
        logger.info("触发 doSentinelConfirmBlock.......:{}", e.getMessage());
        throw new BusinessException(BusinessEnum.NETWORK_SENTINEL_FAIL);
    }

    @Override
    public void doConfirm(ConfirmOrderDoRequest request) {
        String tripCode = request.getTripCode();
        Date date = request.getDate();
        String start = request.getStart();
        String end = request.getEnd();
        List<ConfirmOrderTicketRequest> tickets = request.getTickets();
        // 判断车次是否存在
        // 判断余票是否还有
        // 判断是否在有效期
        // 判断是否有ticket信息
        // 判断同乘客是否已有车次
        DateTime now = DateTime.now();
        // 2. 保存确认订单 状态初始
        ConfirmOrder confirmOrder = BeanUtil.copyProperties(request, ConfirmOrder.class);
        confirmOrder.setId(SnowIdUtil.getLongSnowNextId());
        confirmOrder.setCreateTime(now);
        confirmOrder.setMemberId(AuthContextHolder.getId());
        confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
        confirmOrder.setTickets(JSON.toJSONString(request.getTickets()));
        // 查出余票记录 得到真实库存
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(tripCode, date, start, end);
        logger.info("通往：{} 到 {} 的 {} 列车 的余票记录：{}", start, end, tripCode, dailyTrainTicket);

        // 预扣减余票数量
        reduce(tickets, dailyTrainTicket);

        List<DailyTrainSeat> finalSeatList = new ArrayList<>();

        // 选座
        ConfirmOrderTicketRequest passengerTicket = tickets.get(0);
        String seat = passengerTicket.getSeat();
        if (StringUtils.hasText(seat)) {
            logger.info("本次购票有选座");
            // 查出本次选座的座位类型 计算座位与第一个座位的偏离值
            List<SeatColEnum> colsByTypeList = SeatColEnum.getColsByType(passengerTicket.getSeatTypeCode());
            logger.info("本次选座的座位类型包含的列：{}", colsByTypeList);
            // 组成和前端两排选座一样的列表
            List<String> referSeatList = new ArrayList<>(colsByTypeList.size() * 2);
            for (int i = 1; i <= 2; i++) {
                for (SeatColEnum seatColEnum : colsByTypeList) {
                    referSeatList.add(seatColEnum.getCode() + i);
                }
            }
            logger.info("用于作参照的两排座位：{}", referSeatList);
            List<Integer> offsetList = new ArrayList<>();
            // 绝对偏移值 在参照座位列表中的位置
            List<Integer> aboluteOffsetList = new ArrayList<>();
            for (ConfirmOrderTicketRequest ticket : tickets) {
                int index = referSeatList.indexOf(ticket.getSeat());
                aboluteOffsetList.add(index);
            }
            logger.info("计算得到所有座位的绝对偏移值：{}", aboluteOffsetList);
            for (Integer index : aboluteOffsetList) {
                int offset = index - aboluteOffsetList.get(0);
                offsetList.add(offset);
            }
            logger.info("计算得到所有座位的相对第一个座位的偏移值：{}", offsetList);
            getSeat(finalSeatList, date, tripCode, passengerTicket.getSeatTypeCode(), passengerTicket.getSeat().split("")[0], offsetList, dailyTrainTicket.getStartIndex().intValue(), dailyTrainTicket.getEndIndex().intValue());
        } else {
            logger.info("本次购票不选座位");
            for (ConfirmOrderTicketRequest ticket : tickets) {
                getSeat(finalSeatList, date, tripCode, ticket.getSeatTypeCode(), null, null, dailyTrainTicket.getStartIndex().intValue(), dailyTrainTicket.getEndIndex().intValue());
            }
        }
        // 选中座位后事务处理
        logger.info("最后选定的座位列表：{}", finalSeatList);

        transactionTemplate.executeWithoutResult(status -> {
            try {
                // 初始化订单
                confirmOrderMapper.insert(confirmOrder);
                // 订单之后的处理
                afterConfirmOrderService.afterDoConfirmOrder(dailyTrainTicket, finalSeatList, tickets, confirmOrder);

            } catch (Exception e) {
                status.setRollbackOnly();
                throw new RuntimeException(e.getMessage());
            }
        });
    }


    private void reduce(List<ConfirmOrderTicketRequest> tickets, DailyTrainTicket dailyTrainTicket) {
        for (ConfirmOrderTicketRequest ticket : tickets) {
            String seatTypeCode = ticket.getSeatTypeCode();
            SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            switch (seatTypeEnum) {
                case YDZ -> {
                    int ydz = dailyTrainTicket.getYdz() - 1;
                    if (ydz <= 0) {
                        throw new BusinessException(BusinessEnum.NO_MORE_SEATS_AVAILABLE);
                    }
                    dailyTrainTicket.setYdz(ydz);
                }
                case EDZ -> {
                    int edz = dailyTrainTicket.getYdz() - 1;
                    if (edz <= 0) {
                        throw new BusinessException(BusinessEnum.NO_MORE_SEATS_AVAILABLE);
                    }
                    dailyTrainTicket.setEdz(edz);
                }
                case RW -> {
                    int rw = dailyTrainTicket.getYdz() - 1;
                    if (rw <= 0) {
                        throw new BusinessException(BusinessEnum.NO_MORE_SEATS_AVAILABLE);
                    }
                    dailyTrainTicket.setRw(rw);
                }
                case YW -> {
                    int yw = dailyTrainTicket.getYdz() - 1;
                    if (yw <= 0) {
                        throw new BusinessException(BusinessEnum.NO_MORE_SEATS_AVAILABLE);
                    }
                    dailyTrainTicket.setYw(yw);
                }
            }
        }

    }

    private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String code, String seatType, String column, List<Integer> offsetList, Integer startIndex, Integer endIndex) {
        List<DailyTrainCarriage> dailyTrainCarriages = dailyTrainCarriageService.queryDailyTrainCarriage(date, code, seatType);
        logger.info("符号条件的车厢：{}", dailyTrainCarriages.stream().map(DailyTrainCarriage::getIndex).collect(Collectors.toList()));
        // 循环车厢获取座位数据
        List<DailyTrainSeat> localSeatList = new ArrayList<>();
        for (DailyTrainCarriage dailyTrainCarriage : dailyTrainCarriages) {
            logger.info("开始从车厢 {} 选座", dailyTrainCarriage.getIndex());
            localSeatList = new ArrayList<>();
            List<DailyTrainSeat> seatList = dailyTrainSeatService.selectByCarriage(date, code, dailyTrainCarriage.getIndex());
            logger.info("车厢 {} 的座位数：{}", dailyTrainCarriage.getIndex(), seatList.size());
            for (DailyTrainSeat dailyTrainSeat : seatList) {
                String col = dailyTrainSeat.getCol();
                Integer carriageSeatIndex = dailyTrainSeat.getCarriageSeatIndex();

                // 座位不能选择第二次
                boolean alreadyChooseFlag = false;
                for (DailyTrainSeat trainSeat : finalSeatList) {
                    if (trainSeat.getId().equals(dailyTrainSeat.getId())) {
                        alreadyChooseFlag = true;
                        break;
                    }
                }
                if (alreadyChooseFlag) {
                    logger.info("座位：{} 被选中过 不能重复选中 继续判断下一个座位", carriageSeatIndex);
                    continue;
                }

                // 判断column
                if (StrUtil.isBlank(column)) {
                    logger.info("无选座");
                } else {
                    if (!col.equals(column)) {
                        logger.info("座位 {} 列值不对，继续判断下一个座位，当前列值：{}，目标列值：{}", carriageSeatIndex, col, column);
                        continue;
                    }
                }

                // 选座
                boolean isChoose = callSell(dailyTrainSeat, startIndex, endIndex);
                if (isChoose) {
                    logger.info("第一个座位 选中");
                    localSeatList.add(dailyTrainSeat);
                } else {
                    logger.info("第一个座位 未选中");
                    continue;
                }

                // 根据offset 选剩下的座位
                boolean isGetAllOffsetSeat = true;
                if (CollUtil.isNotEmpty(offsetList)) {
                    logger.info("有偏移值：{}，校验偏移的座位是否可选", offsetList);
                    for (int i = 1; i < offsetList.size(); i++) {

                        Integer offset = offsetList.get(i);
                        // 座位索引从1开始
                        int nextIndex = carriageSeatIndex + offset - 1;

                        // 选座在同一车厢
                        if (nextIndex >= seatList.size()) {
                            logger.info("座位{}不可选，偏移后的索引超出了这个车厢的座位数", nextIndex);
                            isGetAllOffsetSeat = false;
                            break;
                        }

                        DailyTrainSeat nextSeat = seatList.get(nextIndex);
                        boolean isChooseNext = callSell(nextSeat, startIndex, endIndex);
                        if (isChooseNext) {
                            logger.info("Next座位：{} 选中", nextSeat.getCarriageSeatIndex());
                            localSeatList.add(nextSeat);
                        } else {
                            logger.info("Next座位不可选");
                            isGetAllOffsetSeat = false;
                            break;
                        }
                    }
                }

                if (!isGetAllOffsetSeat) {
                    localSeatList = new ArrayList<>();
                    continue;
                }

                // 保存选好的座位
                finalSeatList.addAll(localSeatList);
                return;
            }
        }
    }

    /**
     * 计算某座位在区间内是否可卖
     */
    private boolean callSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex) {
        String sell = dailyTrainSeat.getSell();
        String sellPart = sell.substring(startIndex, endIndex);
        if (Integer.parseInt(sellPart) > 0) {
            logger.info("座位：{} 在本次车站区间{}~{}已售出过票，不可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            return false;
        } else {
            logger.info("座位：{} 在本次车站区间{}~{}未售出过票，可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            String curSell = sellPart.replace('0', '1');
            curSell = StrUtil.fillBefore(curSell, '0', endIndex);
            curSell = StrUtil.fillAfter(curSell, '0', sell.length());
            // 当前售票curSell 与运算 库里sell 得到最后的curSell
            int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
            String newSell = NumberUtil.getBinaryStr(newSellInt);
            newSell = StrUtil.fillBefore(newSell, '0', sell.length());
            logger.info("座位：{} 被选中，原售票信息：{}，车站区间：{}~{}，即：{}，最终售票信息：{}", dailyTrainSeat.getCarriageSeatIndex(), sell, startIndex, endIndex, curSell, newSell);
            dailyTrainSeat.setSell(newSell);
            return true;
        }
    }

}
